Android IPC 您所在的位置:网站首页 android ipc框架 Android IPC

Android IPC

#Android IPC | 来源: 网络整理| 查看: 265

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第4天,点击查看活动详情

前言

前面文章我们重点分析了Linux的进程划分,以及为了减少一次拷贝的内存映射技术,本篇文章我们就来介绍Android中使用最多也最具有代表性的IPC方式:AIDL。

本篇文章主要侧重AIDL的使用细节,然后重点分析AIDL生成的文件,通过手写AIDL生成文件来大致理解Binder机制,话不多说,直接开整。

正文

我们先来看看AIDL的使用。

AIDL使用

AIDL的原理是通过Binder来实现,而且是C/S架构,所以AIDL分为服务端和客户端2个方面来使用:

服务端:服务端首先需要创建一个Service用来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中声明,最后在Service中实现这个AIDL接口。

从这个简单描述我们知道,作为服务端必须建立在Service上,这也非常好理解,Service作为四大组件之一,由Android系统统一管理,同时自己就可以被其他进程所启动;

客户端:客户端首先就需要绑定服务端的Service,绑定成功后,将服务端返回的Binder对象转成AIDL接口所属的类型,接着就可以调用AIDL接口中的方法了。

从客户端实现我们可知这里的关键就是Binder了,本篇文章后面我们在使用AIDL时会简单说明一些Binder相关类的作用。

为了更好地说明AIDL使用的细节,这里我使用俩个APP交互的方式,而不是一个APP开启多进程。

AIDL接口的创建

AIDL接口的的创建非常重要,首先是需要定义在不同进程之间传递的自定义类型,比如我这里定义为Book:

//包名 package com.zyh.ipc; import android.os.Parcel; import android.os.Parcelable; //必须实现Parcelable接口 public class Book implements Parcelable { public int BookId; public String BookName; ...省略 } 复制代码

这是一个Book.java类,必须实现Parcelable接口,然后就是存放该类的目录,这里推荐把涉及到AIDL通信的自定义类都放到一个相同的目录中,这是因为俩个进行跨进程通信的APP会对自定义的类型对象进行序列化和反序列化,而这种序列化方式会记录类的信息,即使俩个APP中都定义了Book类,而且成员变量都一样,但是因为包名不一样,同样是无法反序列化的。

定义完自定义类型后,凡是在AIDL文件中用到的自定义Parcelable类型,都必须新建一个和它同名的AIDL文件,并且在其中声明它为Parcelable类型,而AIDL文件的目录,可以使用AS直接创建出来,这里在AIDL目录下创建一个Book.aidl:

image.png

//Book.aidl package com.zyh.ipc; parcelable Book; 复制代码

再然后我们就可以定义AIDL接口了,该接口就是定义IPC通信的内容,注意这个接口和Java接口有一点不一样,它是只支持方法的,不支持声明静态常量,在本例中创建管理Book的接口IBookManager.aidl:

//包名 package com.zyh.ipc; //必须手动导入自定义的Parcelable类型 import com.zyh.ipc.Book; //接口 interface IBookManager { List getBookList(); void addBook(in Book book); } 复制代码

这里需要注意俩点:

就是前面定义的自定义Parcelable类型,在AIDL文件中必须手动导入。 在AIDL文件中,并不是所有的数据类型都可以使用的,AIDL文件支持的数据类型如下: 基本数据类型,包括int、long、char、boolean等; String和CharSequence; List只支持ArrayList,里面元素必须能被AIDL支持; Map只支持HashMap,里面的key和value的类型也必须被AIDL支持; Parcelable,即实现了Parcelable接口的对象。

创建完AIDL文件以及AIDL用到的自定义Parcelable类型,这时就需要把这些文件从服务端APP拷贝一份到客户端APP中,注意类的包名千万不要改变,当一端的类结构发生改变,在另一端必须替换为一样的。

服务端的实现

在定义完AIDL接口后,需要Build一下项目,这时在app/build/generated/aidl_source_out_dir/debug目录下会生成一个和AIDL接口同名的java文件,即IBookManager.java,关于这个文件我们后面会仔细分析。

这里先来看一下服务端如何实现,前面说了需要通过Service,直接定义一个服务BookManagerService:

class BookManagerService : Service() { //服务端的书籍列表,这里需要使用线程安全的类来保存 private val mBookList = CopyOnWriteArrayList() //实现接口的Binder private val mBinder = object : IBookManager.Stub(){ override fun getBookList(): MutableList { return mBookList } override fun addBook(book: Book?) { mBookList.add(book!!) } } //onBind回调中返回我们定义的Binder override fun onBind(intent: Intent): IBinder { return mBinder } //在服务创建时,往Book列表中添加俩本书 override fun onCreate() { super.onCreate() mBookList.add(Book(11,"Android")) mBookList.add(Book(22,"Ios")) } } 复制代码

这个类涉及的知识点比较多,我们来挨个分析:

保存Book的列表为什么要使用CopyOnWriteArrayList这个线程安全的类型,这是因为getBookList()和addBook()这俩个方法会在线程池中运行,当有多个客户端绑定该服务,同时调用这俩个方法,使用线程安全的集和可以保证数据正确。

类似的集和还有ConcurrentHashMap线程安全的集和。

这个onBind方法返回的类型是IBinder,这里的IBinder是一个接口,可以这么简单理解:所有通过Binder机制进行IPC的对象,都必须实现该类。

这里mBinder是IBookManager.Stud类的实例,这个类是AIDL文件生成的文件,该类定义如下:

public static abstract class Stub extends android.os.Binder implements com.zyh.ipc.IBookManager 复制代码

可以发现它继承至Binder,这个Binder就是IBinder的实现类,同时实现了IBookManager接口,根据IBinder接口的特性,这里我们就可以认为这个mBinder就具有了跨进程的能力。

客户端的实现

前面既然说了对象实现了IBinder就有了跨进程的能力,那可以想象在客户端应该就是拿到刚刚在服务端定义的Binder对象,然后对这个Binder进行操作,所以客户端代码如下:

//通过bindService连接客户端APP的服务 findViewById(R.id.bindService).setOnClickListener { bindService( //这里的Intent除了需要action,还必须设置服务端APP的包名 Intent("com.zyh.ipc.BookManagerService").setPackage("com.zyh.ipc"), conn, BIND_AUTO_CREATE ) } 复制代码 //服务连接成功和断开的回调 private val conn = object : ServiceConnection{ override fun onServiceConnected(name: ComponentName?, service: IBinder?) { Log.i("ipc", "onServiceConnected: ") //通过asInterface方法把IBinder对象转成IBookManager类型 bookManager = IBookManager.Stub.asInterface(service) } override fun onServiceDisconnected(name: ComponentName?) { Log.i("ipc", "onServiceDisconnected: ") } } 复制代码 //查看书籍列表 findViewById(R.id.getBook).setOnClickListener { val list = bookManager?.bookList //看一下这个list的类型 Log.i("ipc", "onCreate: ${list?.javaClass?.name}") for (book in list!!) { Log.i("ipc", "onServiceConnected: ${book.BookId} ${book.BookName}") } } 复制代码

这里的核心代码就是IBinder对象的asInterface了,这里可以看成就获取到了服务端的Binder了(真实原理我们后面再说)。

我们看一下打印:

2022-07-28 15:11:54.804 21595-21595/com.zyh.client I/ipc: onCreate: java.util.ArrayList 2022-07-28 15:11:54.804 21595-21595/com.zyh.client I/ipc: onServiceConnected: 11 Android 2022-07-28 15:11:54.804 21595-21595/com.zyh.client I/ipc: onServiceConnected: 22 Ios 复制代码

这里会发现list的类型是ArrayList,为什么说这个呢 原因是刚开始我们说AIDL支持List,但是只支持ArrayList,但是我们在服务端代码中却用了CopyOnWriteArrayList,这个类定义:

public class CopyOnWriteArrayList implements List, RandomAccess, Cloneable, java.io.Serializable 复制代码

会发现它虽然是名字和ArrayList相关,但是它却不是继承至ArrayList,这就说明了数据在传输过程中,Binder对数据类型做了处理。

这里还有注意一点,就是客户端调用完getBookList方法后,会挂起当前线程,直到等待结果返回,是一个同步方法,所以当在服务端该方法执行时间过长,在主线程调用该方法可能会导致ANR。

这里我们可以做个小小总结:

服务端和客户端使用同样的AIDL文件,会生成同样的类,而生成类IBookManager.Stub是继承至Binder,且实现了IBookManager接口。 服务端的作用就是实现IBookManager.Stub接口,实现业务需求。 客户端通过拿到Binder对象,然后转成IBookManager类型,和服务端进行通信。 使用回调

在前面我们已经说了AIDL的简单使用了,现在我们加个功能,就是在AIDL中使用回调。

这个功能在开发车机、电视等系统中经常用,比如导航APP在运行时,一般需要在Launcher显示出导航的简短信息,这个导航实时信息就是导航APP通过回调给的。

在本章例子中,我们添加一个回调监听,希望服务端每隔一段时间能主动回调当前Book的信息。首先我们定义接口,这个接口也必须定义为aidl类型:

// IOnBookListener.aidl package com.zyh.ipc; import com.zyh.ipc.Book; //返回所有Book interface IOnBookListener { void onAllBook(in List books); } 复制代码

这里为什么要定义为AIDL接口,因为接口中的方法是需要跨进程调用的,方法回调的数据是需要序列化和反序列化的,所以一般的Java接口肯定满足不了;

定义完新接口后,需要在原来接口中添加注册和反注册的方法:

package com.zyh.ipc; //必须手动导入 import com.zyh.ipc.Book; import com.zyh.ipc.IOnBookListener; interface IBookManager { List getBookList(); void addBook(in Book book); //添加监听 void registerListener(IOnBookListener listener); //移除监听 void unregisterListener(IOnBookListener listener); } 复制代码

修改完AIDL接口,需要Build一下项目,然后在服务端进行简单修改:

class BookManagerService : Service() { private val mBookList = CopyOnWriteArrayList() //listener列表 private val mListenerList = CopyOnWriteArrayList() private val mBinder = object : IBookManager.Stub(){ override fun getBookList(): MutableList { return mBookList } override fun addBook(book: Book?) { mBookList.add(book!!) } override fun registerListener(listener: IOnBookListener?) { if (mListenerList.contains(listener)){ Log.i("BMS", "registerListener: already exist") }else{ mListenerList.add(listener) } } override fun unregisterListener(listener: IOnBookListener?) { if (mListenerList.contains(listener)){ mListenerList.remove(listener) }else{ Log.i("BMS", "unregisterListener: not exist") } } } override fun onBind(intent: Intent): IBinder { return mBinder } override fun onCreate() { super.onCreate() mBookList.add(Book(11,"Android")) mBookList.add(Book(22,"Ios")) //开启线程循环回调book信息 Thread{ while (true){ Thread.sleep(5000) for (listener in mListenerList){ Log.i("BMS", "onCreate: 回调信息") listener.onAllBook(mBookList) } } }.start() } } 复制代码

上面代码非常简单,和保存Book一样也新建一个listener列表,然后在onCreate方法中,每隔5s回调一次Book信息,同时对Binder实现类添加了注册和反注册的功能。

写完服务端代码,必须把修改涉及的文件拷贝到客户端,这一步千万不能出错,在客户端中一样先Build,然后在服务连接成功后,创建监听的实例,给注册到服务端:

private val conn = object : ServiceConnection{ override fun onServiceConnected(name: ComponentName?, service: IBinder?) { Log.i("ipc", "onServiceConnected: ") bookManager = IBookManager.Stub.asInterface(service) //这里注意必须是创建IOnBookListener.Stub的实例 bookManager?.registerListener(object : IOnBookListener.Stub(){ override fun onAllBook(books: MutableList?) { for (book in books!!) { Log.i("ipc", "onAllBook: ${book.BookId} ${book.BookName}") } } }) } override fun onServiceDisconnected(name: ComponentName?) { Log.i("ipc", "onServiceDisconnected: ") } } 复制代码

这里要注意的上面已经注释了,就是在跨进程中,我们定义监听的接口必须是aidl类型的,而非普通的Java类型,所以客户端在注册时必须是传递生成类Stub的对象,至于原因后面会细说;这里创建了IOnBookListener.Stub的实例,但是通过跨进程后,客户端实例和服务端实例将不是同一个,上面代码的运行如下:

image.png

可以发现可以正常实现功能,每5S回调一次服务的Book列表信息。

既然我们可以注册listener,当然也需要测试一下反注册listener,但是在反注册listener中,我们会发现如下打印:

image.png

服务端居然提醒找不到该listener,并且客户端还是一直在回调,这不是大坑吗 这就是AIDL使用回调时需要注意的点。

我们在客户端新建listener对象,通过Binder传递到服务端后,会生成一个全新的对象,这是因为IPC的本质是序列化和反序列化,对象是需要序列化后才可以传输,所以这个listener在客户端和服务端俩个进程就不是一个对象。

既然不是同一个对象,那回调功能咋是正常的呢,这个后面分析AIDL生成文件时细说,这里先解决如何正常反注册。

解决的办法非常简单,使用RemoteCallbackList来代替前面的CopyOnWriteArrayList集和,从名字就可以看出这个是专门用来存放远程回调的,我们可以简单看一下该类的定义:

public class RemoteCallbackList 复制代码

可以发现它是一个泛型类,而泛型类型是IInterface,这个接口很重要,定义如下:

//Binder接口的基类,当我们定义一个跨进程的接口时,必须继承这个接口 public interface IInterface { //由于跨进程的真实原理是通过Binder,所以通过这个把接口和Binder对象联系起来。 //我们把接口转成Binder要用这个,而不是直接强转。 public IBinder asBinder(); } 复制代码

RemoteCallbackList原理非常简单,在它的内部有一个Map结构专门用来保存所有AIDL回调,这个Map的key是IBinder类型,value是Callback类型:

ArrayMap mCallbacks = new ArrayMap(); 复制代码

而当我们注册一个回调时,其中的key和value是如下方法获取:

IBinder binder = callback.asBinder(); Callback cb = new Callback(callback, cookie); unregister(callback); binder.linkToDeath(cb, 0); mCallbacks.put(binder, cb); 复制代码

到这里我们就明白了,虽然说跨进程传输中,在客户端新建的对象和服务端不是同一个,但是这俩个对象对应的Binder对象是同一个,所以当注册和反注册时可以针对对应的Binder来进行增删。

同时RemoteCallbackList还有一个很有用的功能,就是当客户端进程终止后,它能自动移除客户端注册的listener,而且RemoteCallbackList内部自动实现了线程同步功能,所以我们使用它来注册和反注册时,不需要做额外的线程同步工作。

重连机制

为了程序的健壮性,当服务端进程意外停止时,我们需要重新连接服务。在这里重新连接服务,有俩种方法:

可以给在服务端运行的Binder设置DeathRecipient监听回调,当Binder死亡时,我们会收到binderDied方法的回调,在binderDied方法中我们可以重连远程服务。

代码如下:

private val deathRecipient = object : IBinder.DeathRecipient { override fun binderDied() { Log.i("ipc", "binderDied: 调用 需用重连Service") } } 复制代码

定义完deathRecipient后,在bindService成功的回调中给binder添加该死亡监听即可:

override fun onServiceConnected(name: ComponentName?, service: IBinder?) { Log.i("ipc", "onServiceConnected: ") bookManager = IBookManager.Stub.asInterface(service) //注册Binder死亡回调 service?.linkToDeath(deathRecipient, 0) bookManager?.registerListener(listener) } 复制代码 通过bindService的回调,即ServiceConnection,可以在其中的onServiceDisconnected方法中进行重连机制,代码如下: override fun onServiceDisconnected(name: ComponentName?) { Log.i("ipc", "onServiceDisconnected: ") //需要进行重连 } 复制代码

到这里,AIDL的使用我们基本就说完了。其实AIDL文件是一套java代码生成规则,可以看成是给编译器来使用的一套规则,通过AIDL文件,编译器生成符合跨进程通信的java代码。 这样的好处就是不用让开发者编写复杂的跨进程代码。

AIDL生成文件分析

会使用AIDL还是不够的,必须要能看得懂AIDL文件所生成的java文件,因为在很多源码中,都是直接使用java代码来编码的。

前面说了当IPC时我们需要定义.aidl接口,这个接口就是IPC的内容,当定义了一个IBookManager.aidl接口,这个接口肯定不能直接起作用,它会生成一个IBookManager.java,代码如下:

package com.zyh.ipc; //IBookManager是一个接口,继承至IInterface public interface IBookManager extends android.os.IInterface { //对IBookManager接口的默认实现 public static class Default implements com.zyh.ipc.IBookManager { @Override public java.util.List getBookList() throws android.os.RemoteException { return null; } @Override public void addBook(com.zyh.ipc.Book book) throws android.os.RemoteException { } @Override public android.os.IBinder asBinder() { return null; } } //IPC通信实现的存根类,这里实现了IBookManager接口,同时继承了Binder public static abstract class Stub extends android.os.Binder implements com.zyh.ipc.IBookManager { //唯一描述符 private static final java.lang.String DESCRIPTOR = "com.zyh.ipc.IBookManager"; //构造函数,关联接口 public Stub() { //Binder的方法,将特定的接口与Binder关联。当调用完该方法后,queryLocalInterface方法将会根据唯一描述符返回特定的接口 this.attachInterface(this, DESCRIPTOR); } //把一个IBinder对象转换成IBookManager对象,如果是跨进程通信则需要生成一个代理类 public static com.zyh.ipc.IBookManager asInterface(android.os.IBinder obj) { if ((obj==null)) { return null; } //通过唯一描述符,找到跨进程的接口对象 android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); //如果跨进程的类型是IBookManager,说明是没有跨进程的 if (((iin!=null)&&(iin instanceof com.zyh.ipc.IBookManager))) { return ((com.zyh.ipc.IBookManager)iin); } //否则返回Stub.Proxy类型对象 return new com.zyh.ipc.IBookManager.Stub.Proxy(obj); } //这个是IInterface接口的方法,即需要把该接口强转为Binder对象 @Override public android.os.IBinder asBinder() { return this; } //交换的核心代码,返回值返回true则表示调用成功。 //code表示唯一标识符,即该调用哪个方法 //data表示客户端发送给服务端的数据 //reply表示服务端返回的数据 @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { java.lang.String descriptor = DESCRIPTOR; switch (code) { //开始交换,返回值写入唯一表示符 case INTERFACE_TRANSACTION: { reply.writeString(descriptor); return true; } //获取book列表 case TRANSACTION_getBookList: { //验证当前接口是否匹配 data.enforceInterface(descriptor); //这个this表示IBookManager接口的方法 java.util.List _result = this.getBookList(); reply.writeNoException(); reply.writeTypedList(_result); return true; } case TRANSACTION_addBook: { //验证当前接口是否匹配 data.enforceInterface(descriptor); com.zyh.ipc.Book _arg0; if ((0!=data.readInt())) { //data是Parcel数据,这里根据CREATOR创建出Book对象 _arg0 = com.zyh.ipc.Book.CREATOR.createFromParcel(data); } else { _arg0 = null; } //同样是调用IBookManager接口的方法 this.addBook(_arg0); reply.writeNoException(); return true; } default: { return super.onTransact(code, data, reply, flags); } } } //IBookManager接口的代理类,当真进行IPC通信时,调用该类 private static class Proxy implements com.zyh.ipc.IBookManager { private android.os.IBinder mRemote; //远程的IBinder对象 Proxy(android.os.IBinder remote) { mRemote = remote; } //IInterface接口方法 @Override public android.os.IBinder asBinder() { return mRemote; } public java.lang.String getInterfaceDescriptor() { return DESCRIPTOR; } @Override public java.util.List getBookList() throws android.os.RemoteException { //请求数据 android.os.Parcel _data = android.os.Parcel.obtain(); //响应数据 android.os.Parcel _reply = android.os.Parcel.obtain(); java.util.List _result; try { _data.writeInterfaceToken(DESCRIPTOR); //调用刚刚定义的transact方法,会回调Stub类中的onTransact方法 boolean _status = mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0); //当调用不成功且默认实现不为空时,即是非IPC通信 if (!_status && getDefaultImpl() != null) { return getDefaultImpl().getBookList(); } _reply.readException(); _result = _reply.createTypedArrayList(com.zyh.ipc.Book.CREATOR); } finally { _reply.recycle(); _data.recycle(); } return _result; } @Override public void addBook(com.zyh.ipc.Book book) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); if ((book!=null)) { _data.writeInt(1); book.writeToParcel(_data, 0); } else { _data.writeInt(0); } boolean _status = mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0); if (!_status && getDefaultImpl() != null) { getDefaultImpl().addBook(book); return; } _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } } public static com.zyh.ipc.IBookManager sDefaultImpl; } //静态变量,每个方法对应一个静态变量 static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); //设置默认的实现 public static boolean setDefaultImpl(com.zyh.ipc.IBookManager impl) { //当客户端和服务端在同一个进程中会被调用 if (Stub.Proxy.sDefaultImpl != null) { throw new IllegalStateException("setDefaultImpl() called twice"); } if (impl != null) { Stub.Proxy.sDefaultImpl = impl; return true; } return false; } public static com.zyh.ipc.IBookManager getDefaultImpl() { return Stub.Proxy.sDefaultImpl; } } //java文件的接口 public java.util.List getBookList() throws android.os.RemoteException; //java文件的接口 public void addBook(com.zyh.ipc.Book book) throws android.os.RemoteException; } 复制代码

上面生成的代码还是蛮多的,看起来有点乱的原因是定义了太多内部类,首先就是IBookManager.java这个类,它继承了IInterface接口,同时它自己也是一个接口,所有可以在Binder中传输的接口都必须继承至IInterface接口。

同时在IBookManager.java中定义了俩个方法,这俩个方法就是我们在aidl中定义的方法,是IPC希望实现的功能接口。同时定义了俩个静态整形的id分别用来标识这俩个方法,这个俩个id用于标识在transact过程中客户端所请求的到底是哪个方法。

然后是IBookManager.java中的第一个内部类:Default,该类是默认实现,可以无需关注。

IBookManager.java中第二个内部类:Stub,其中Stub的意思是"存根",表示一些接口的实现类。该类是核心,它首先是继承至Binder,同时实现了IBookManager接口,下面详细列举一下该类中的实现细节:

DESCRIPTOR,这个是Binder的唯一表示,一般用当前Binder的类名表示。 asInterface,用于将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象,这种转换是区分进程的,如果客户端和服务端在同一个进程,那么此方法返回的就是服务端的Stub对象本身,否则返回的是封装后的Stub.proxy对象。 asBinder,用于返回当前Binder对象。 onTransact,该方法会运行在服务端的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由该方法来处理。

该方法的参数非常重要,服务端通过code可以确定客户端所请求的目标方法是什么,然后从data中取出目标方法所需要的参数,然后执行目标方法,当目标方法执行完毕后,会向reply中写入返回值。这里注意,如果此方法返回false,那么客户端的请求会失败,因此我们可以利用这个特性来做权限验证,毕竟我们不希望随便一个进程都能远程调用服务。

Proxy类,该类是Stub的内部类,当客户端和服务端不在一个线程时,asInterface就会创建Proxy类的对象,然后构造函数传入的IBinder对象还是服务端的Binder对象。 Proxy#getBookList,这个方法运行在客户端中,当客户端调用此方法时,它的内部实现是这样的:首先创建该方法所需要的输入型Parcel对象_data、输出型Parcel对象_reply和返回值对象List,然后把该方法的参数信息写入_data中,接着调用transact方法来发起RPC请求,同时当前线程挂起;然后服务端的onTransact方法会被调用,知道RPC过程返回后,当前线程继续执行,并且从_reply中取出RPC过程返回的结果。

上面便是AIDL的生成文件的简单分析。

手写一个Binder使用实例

在前面我们也说了.aidl文件只是生成Java代码的规则,而且这里我们也分析了AIDL生成的文件,所以我们就可以不用AIDL来手动创建Binder对象,这样一样可以实现IPC通信。

先声明一个跨进程的接口,该接口只需要继承IInterface接口即可,IInterface有一个asBinder方法,这个接口定义如下: public interface ICustomBookManager extends IInterface { static final java.lang.String DESCRIPTOR = "com.zyh.ipc.IBookManager"; static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); public java.util.List getBookList() throws android.os.RemoteException; public void addBook(com.zyh.ipc.Book book) throws android.os.RemoteException; } 复制代码

这里定义了ICustomBookManager的Java接口,并且定义了俩个方法以及方法的唯一表示符号。

然后是新建实现该接口的Java类,同时继承至Binder: //继承至Binder,同时实现ICustomBookManager接口 public class CustomBookManagerImpl extends Binder implements ICustomBookManager { public CustomBookManagerImpl() { this.attachInterface(this, DESCRIPTOR); } //必须的方法,把IBinder对象转成接口类型 public static ICustomBookManager asInterface(IBinder obj){ if ((obj==null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin!=null)&&(iin instanceof com.zyh.ipc.CustomBookManagerImpl))) { return ((com.zyh.ipc.CustomBookManagerImpl)iin); } return new com.zyh.ipc.Proxy(obj); } //模板代码,在服务端被调用 @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { java.lang.String descriptor = DESCRIPTOR; switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(descriptor); return true; } case TRANSACTION_getBookList: { data.enforceInterface(descriptor); java.util.List _result = this.getBookList(); reply.writeNoException(); reply.writeTypedList(_result); return true; } case TRANSACTION_addBook: { data.enforceInterface(descriptor); com.zyh.ipc.Book _arg0; if ((0!=data.readInt())) { _arg0 = com.zyh.ipc.Book.CREATOR.createFromParcel(data); } else { _arg0 = null; } this.addBook(_arg0); reply.writeNoException(); return true; } default: { return super.onTransact(code, data, reply, flags); } } } @Override public List getBookList() throws RemoteException { return null; } @Override public void addBook(Book book) throws RemoteException { } //返回Binder @Override public IBinder asBinder() { return this; } } 复制代码 在上面代码我们知道当是真的IPC时,是需要一个代理类的,由代理类来进行数据转换和接口请求: public class Proxy implements ICustomBookManager { private android.os.IBinder mRemote; Proxy(android.os.IBinder remote) { mRemote = remote; } @Override public List getBookList() throws RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); java.util.List _result; try { _data.writeInterfaceToken(DESCRIPTOR); boolean _status = mRemote.transact(IBookManager.Stub.TRANSACTION_getBookList, _data, _reply, 0); _reply.readException(); _result = _reply.createTypedArrayList(com.zyh.ipc.Book.CREATOR); } finally { _reply.recycle(); _data.recycle(); } return _result; } @Override public void addBook(Book book) throws RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); if ((book!=null)) { _data.writeInt(1); book.writeToParcel(_data, 0); } else { _data.writeInt(0); } boolean _status = mRemote.transact(IBookManager.Stub.TRANSACTION_addBook, _data, _reply, 0); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } } @Override public IBinder asBinder() { return mRemote; } } 复制代码

可以发现上面代码和生成的代码几乎一样,只不过可读性多了很多,里面关键的地方可以归纳如下几点:

跨进程的接口必须要继承IInterface接口。 接口的Java实现类必须继承至Binder。 asInterface必不可少,可以把Binder对象转成接口类型,这里由于要区分是否是跨进程,如果是跨进程需要调用Proxy代理类来辅助。 onTransact是调用transact后回调,是服务端处理请求,在线程池中执行。 Proxy代理类就是用来调用接口,以及方法相关参数的序列化与反序列化过程。 总结

不知不觉本篇文章的子树已经非常多了,内容较多,下面做个总结:

在AIDL使用篇,主要就是AIDL文件的写法要尤其注意,以及使用回调和重连机制的使用。 在AIDL生成文件分析中,我们要明确看出其不同内部类的作用。 在手写Binder通信时,我们需要知道IInterface的含义,以及实现接口和继承至Binder的Java类。 其中要会根据是否跨进程来选择是否使用代理类,其中onTransact是在服务端线程池中调用,而Proxy中的方法是同步执行,会挂起。

笔者水平有限,如有问题,欢迎大家评论指正。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有